/*
*  Copyright (C) 2013 Michael Mallat michael.mallat@gmail.com
*
*  This file is part of MyTribbles
*
*  MyTribbles is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  MyTribbles is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with MyTribbles.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <time.h>
#include <stdlib.h> 
#include <stdio.h>
#include <list>
#include <cmath>
#include <map>

#include "sqlite3.h"

#include "main.h"
#include "tribble.h"
#include "field.h"

/* Global variable declarations */
Field **arena;
std::list<Tribble> GlobalTribbleList;

/* Overloaded stuff */

std::ostream& operator<<(std::ostream &strm, const Field& f)
{
    return strm << "Field("<<f.getX()<<", "<<f.getY()<<")"; 
}

std::ostream& operator<<(std::ostream &strm, const Tribble& t)
{
    return strm << "Tribble("<<t.getX()<<","<<t.getY()<<") "
                << " Age: "<<t.getAge()
                << " Energy: "<<t.getEnergy();

}

/* Functions */

/* 
 * Flipping a coin
 */
bool getFlippedCoin()
{
    int nRand = rand() % 2;

    if (nRand)
    {
        return true;
    }

    return false;
}

/*
 * Init the random generator.
 * This may be replaced some day
 * 
 */
void initRandomGenerator()
{
    srand (time(NULL));
}

/* 
 * Get a random number in a given range
 */
int getRandomInt(unsigned int nMin, unsigned int nMax)
{
    return rand() % (nMax - nMin) + nMin;
}

/*
 * Returns a random letter, capitalized or lower case
 */
char getRandomLetter()
{
    char c;
    while(1)
    {
        c = static_cast<char>(getRandomInt(65, 122));
        if ( (c >= 65 && c <= 90) ||
             (c >= 97 && c <= 122))
        {
            return c;
        }
    }
}

/*
 * Generate a start tribble
 */
Tribble createStartTribble()
{
    std::string name; 
    int nNameLength = getRandomInt(5, 11);
    
    for (int i = 0; i < nNameLength; i++)
    {
         name += getRandomLetter();
    }

    Tribble t(name);

    t.setAge(getRandomInt(5, 50));
    t.setEnergy(getRandomInt(12, 100));
    t.setDirection(getRandomInt(0, 5));

    for (int i = 0; i < TRIBBLE_NUMGENES; i++)
    {
        t.setGene(i, getRandomInt(0, 10));
    }

    return t;
}

Tribble createChildTribble(Tribble parent)
{
    std::string name; 
    int nNameLength = getRandomInt(5, 11);
    
    for (int i = 0; i < nNameLength; i++)
    {
         name += getRandomLetter();
    }

    Tribble t(name);
    t.setAge(0);
    t.setEnergy(parent.getEnergy() / 2);
    t.setDirection(getRandomInt(0, 5));
    t.setCoords(parent.getX(), parent.getY());
    
    for (int i = 0; i < TRIBBLE_NUMGENES; i++)
    {
        t.setGene(i, parent.getGene(i));
    }

    if (getFlippedCoin())
    {
        int nRandGene = getRandomInt(0, TRIBBLE_NUMGENES);
        int nDelta = 0;
#if 0
        if (getFlippedCoin())
        {
            for (int i = 0; i < TRIBBLE_NUMGENES; i++)
            {
                if (i != nRandGene)
                {
                    nDelta += t.getGene(i) * 0.33;
                    t.setGene(i, t.getGene(i) * 0.66);
                }
            }
            t.setGene(nRandGene, t.getGene(nRandGene) + nDelta);
        }
        else
#endif
        {
            nDelta = t.getGene(nRandGene) / 2 + 1;
            t.setGene(nRandGene, nDelta);

            nDelta = nDelta / (TRIBBLE_NUMGENES - 1) + 1;

            for (int i = 0; i < TRIBBLE_NUMGENES; i++)
            {
                if (i != nRandGene)
                {
                    t.setGene(i, t.getGene(i) + nDelta);
                }
            }
        }
    }

    return t;
}

/*
 * Calculate direction flag based on the tribbles genes
 *
 */
DirectionType getNewDirection(Tribble t)
{
    float nSumOfPower;
    float nTemp[TRIBBLE_NUMGENES];
    float nProb[TRIBBLE_NUMGENES];
    float nRandomDirection = getRandomInt(0, 100) / 100.0;
    
    for (int i = 0; i < TRIBBLE_NUMGENES; i++)
    {
        nSumOfPower += pow(2, t.getGene(i));
    }

    for (int i = 0; i < TRIBBLE_NUMGENES; i++)
    {
        nTemp[i] = pow(2, t.getGene(i)) / nSumOfPower;
    }

    nProb[0] = nTemp[0];
    nProb[1] = nTemp[1] + nProb[0];
    nProb[2] = nTemp[2] + nProb[1];
    nProb[3] = nTemp[3] + nProb[2];
    nProb[4] = nTemp[4] + nProb[3];
    nProb[5] = nTemp[5] + nProb[4];

    if (nRandomDirection <= nProb[0])
    {
        return TRIBBLE_FWD;
    }
    else if (nRandomDirection > nProb[0] && 
             nRandomDirection <= nProb[1])
    {
        return TRIBBLE_HARD_LEFT;
    }
    else if (nRandomDirection > nProb[1] &&
             nRandomDirection <= nProb[2])
    {
        return TRIBBLE_HARD_RIGHT;
    }
    else if (nRandomDirection > nProb[2] &&
             nRandomDirection <= nProb[3])
    {
        return TRIBBLE_LEFT;
    }
    else if (nRandomDirection > nProb[3] &&
             nRandomDirection <= nProb[4])
    {
        return TRIBBLE_RIGHT;
    }
    else if (nRandomDirection > nProb[4])
    {
        return TRIBBLE_BACK;
    }
    else 
    {
        std::cout<<"Should not happen"<<std::endl;
        return TRIBBLE_FWD;
    }
}

std::string DirectionType2String(DirectionType dir)
{
    switch(dir)
    {
        case TRIBBLE_FWD:
            return "TRIBBLE_FWD";
        case TRIBBLE_HARD_LEFT:
            return "TRIBBLE_HARD_LEFT";
        case TRIBBLE_HARD_RIGHT:
            return "TRIBBLE_HARD_RIGHT";
        case TRIBBLE_LEFT:
            return "TRIBBLE_LEFT";
        case TRIBBLE_RIGHT:
            return "TRIBBLE_RIGHT";
        case TRIBBLE_BACK:
            return "TRIBBLE_BACK";
    }
    return "DIRECTION_UNKNOWN";
}

/*
 * Calculates depending on Tribble current position,
 * arena size and direction the new coordinates of the tribble
 * Stores new coordinates in tribble object
 */
void setNewTribblePosition(Tribble *t, DirectionType dir)
{
    int x = t->getX();
    int y = t->getY();

    if (dir == TRIBBLE_FWD)
    {
        if (y == 0)
            y = ARENA_Y - 1;
        else
            y -= 1;
    }
    else if (dir == TRIBBLE_HARD_LEFT) 
    {
        if (x == 0)
            x = ARENA_X - 1;
        else
            x -= 1;
    }
    else if (dir == TRIBBLE_HARD_RIGHT) 
    {
        if (x == ARENA_X - 1)
            x = 0;
        else
            x += 1;
    }
    else if (dir == TRIBBLE_LEFT) 
    {
        if (x == 0)
            x = ARENA_X - 1;
        else
            x -= 1;

        if (y == 0)
            y = ARENA_Y - 1;
        else
            y -= 1;
    }
    else if (dir == TRIBBLE_RIGHT) 
    {
        if (x == ARENA_X - 1)
            x = 0;
        else
            x += 1;

        if (y == 0)
            y = ARENA_Y - 1;
        else
            y -= 1;
    }
    else if (dir == TRIBBLE_BACK)
    {
        if (y == ARENA_Y - 1)
            y = 0;
        else
            y += 1;
    }

    t->setCoords(x, y);
}

/*
 * This prints the arena - for debugging purposes
 * */
void debugPrint(int nTickCounter, int nNumberOfTribbles)
{
    for(int x = 0; x < ARENA_X; ++x)
    {
        for(int y = 0; y < ARENA_Y; ++y)
        {
            if (arena[x][y].getNumberOfTribbles() > 0)
            {
                std::list<Tribble*>::iterator fit, fitBegin, fitEnd;

                fitBegin = arena[x][y].getIteratorBegin();
                if ((*fitBegin)->getEnergy() < 200)
                {
                    std::cout<<"+";
                }
                else
                {
                    std::cout<<"*";
                }
            }
            else if (arena[x][y].isFood())
            {
                std::cout<<".";
            }

            else
            {
                std::cout<<" ";
            } 
        }
        std::cout<<"|"<<std::endl;
    }
    
    std::map<int, int> gene[TRIBBLE_NUMGENES];
    std::list<Tribble>::iterator it;
    std::map<int, int>::iterator mapit;

    int nAge = 0;

    for (it=GlobalTribbleList.begin(); it!=GlobalTribbleList.end(); ++it)
    {
        nAge += it->getAge();
        std::string s = ""; 

        for (int i = 0; i < TRIBBLE_NUMGENES; i++)
        {
            gene[i][it->getGene(i)]++;
        }
    }

    if (GlobalTribbleList.size() > 0)
    {
        nAge /= GlobalTribbleList.size();
    }
    
    std::cout<<"Ctr: "<<nTickCounter;
    std::cout<<" # of Tribbles: "<<nNumberOfTribbles<<" ";
    std::cout<<"AvAge: "<<nAge<<" ";
#if 0
    for (int i = 0; i < TRIBBLE_NUMGENES; i++)
    {
        std::cout<<"G"<<i<<" ";
        for (mapit=gene[i].begin(); mapit!=gene[i].end(); ++mapit)
        {
            std::cout<<mapit->first<<":"<<mapit->second<<" ";
        }
        std::cout<<std::endl;
    }
#endif
    std::cout<<std::endl;
}

int main()
{
    sqlite3* pDb = NULL;
    sqlite3_stmt* query = NULL;
    int ret;  
    int nTickCounter = 0;

    // initialize engine
    if (SQLITE_OK != (ret = sqlite3_initialize()))
    {
        std::cout<<"Failed to init: "<<ret<<std::endl;
    }

    // open connection to a DB
    if (SQLITE_OK != (ret = sqlite3_open_v2("test.db", &pDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)))
    {
        std::cout<<"Failed to open conn: "<<ret<<std::endl;
    }

    // CREATE TABLE "arena" ("x" integer, "y" integer, "food" boolean) 

CREATE TABLE "tribbles" 
(
"name" text,
"energy" integer, 
"age" integer, 
"x" integer,
"y" integer,
"g0" integer,
"g1" integer,
"g2" integer,
"g3" integer,
"g4" integer,
"g5" integer
)


    std::cout<<"Generating arena"<<std::endl;
    
    std::cout<<"Allocating memory"<<std::endl;

    arena = new Field*[ARENA_X];

    for(int i = 0; i < ARENA_X; ++i)
    {
        arena[i] = new Field[ARENA_Y];
    }

    std::cout<<"Setting arena data"<<std::endl;

    for(int x = 0; x < ARENA_X; ++x)
    {
        for(int y = 0; y < ARENA_Y; ++y)
        {
            arena[x][y].setCoords(x, y);
        }
    }
    
    std::cout<<"Initialize random generator"<<std::endl;
    initRandomGenerator();
    
    std::cout<<"Generate and place start Tribbles"<<std::endl;
    for (int i = 0 ; i < 10; i++)
    {
        Tribble b = createStartTribble();
        int x = getRandomInt(0, ARENA_X - 1);
        int y = getRandomInt(0, ARENA_Y - 1);
        b.setCoords(x, y);
        GlobalTribbleList.push_back(b);
        arena[x][y].addTribble(&b);
    }
    
    std::list<Tribble>::iterator it;
    for (it=GlobalTribbleList.begin(); it!=GlobalTribbleList.end(); ++it)
    {
        std::cout<<*it<<std::endl;
    }
    
    // Simulation loop
    while(1)
    {
        usleep(1000);
        if (GlobalTribbleList.size() == 0)
            return 0;

        // Distribute food
        if (nTickCounter % 5 == 0)
        {
            for(int x = 0; x < ARENA_X; ++x)
            {
                for(int y = 0; y < ARENA_Y; ++y)
                {
                    int nFoodDistri = getRandomInt(0, 100);
                    if (nFoodDistri == 2)
                    {
                        arena[x][y].setFood(true);
                    }
                }
            }
        }

        // .) Distribute food ... 
        // .) Increment Age for all Tribbles
        // .) Search for Tribbles with no energy left -> let them die
        // .) Search for Tribbles to old to live on -> let them die
        // .) Search for Tribbles ready for fission -> make 2 Baby-Tribble
        //      -> Generate gene-pool for Baby-Tribble (Mutation!)
        //      -> Baby-Tribbles coords are initially the parents coords
        //      -> Baby-Tribbles inital Energy is 50 % Parent Energy
        //
        // .) Get new Directions for all remaining Tribbles
        // .) Update Coords in Tribble objects
        // .) Clear Tribble-Lists in all Field-Objects
        // .) Update Tribble-Lists with help of Coords in Tribble objects
        // .) Let the feast begin! -> if more than one tribble is on a field, 
        //                            the food is split up equally
        //
        
        for (it=GlobalTribbleList.begin(); it!=GlobalTribbleList.end(); ++it)
        {
            it->incrAge();
            DirectionType dir = getNewDirection(*it);
            setNewTribblePosition(&(*it), dir);

            if (it->getAge() >= 1000) // MAXAGE
            {
               arena[it->getX()][it->getY()].setFood(true);
               it = GlobalTribbleList.erase(it);
               continue;
            }

            if (it->getEnergy() == 0)
            {
               arena[it->getX()][it->getY()].setFood(true);
                it = GlobalTribbleList.erase(it);
                continue;
            }
            else
            {
                it->decrEnergy();
            }

            if (it->getAge() > 800 && it->getEnergy() > 1000)
            {
                // Reproduce
                Tribble a = createChildTribble(*it);
                Tribble b = createChildTribble(*it);
                GlobalTribbleList.push_back(a);
                GlobalTribbleList.push_back(b);
                it = GlobalTribbleList.erase(it);
                continue;
            }
        }

        // Clear Tribbles
        for(int x = 0; x < ARENA_X; ++x)
        {
            for(int y = 0; y < ARENA_Y; ++y)
            {
                arena[x][y].clearTribbles();
            }
        }

        for (it=GlobalTribbleList.begin(); it!=GlobalTribbleList.end(); ++it)
        {
            int x = it->getX();
            int y = it->getY();
            arena[x][y].addTribble(&(*it));
        }

        nTickCounter++;
        debugPrint(nTickCounter, GlobalTribbleList.size());
        
        // Clear Tribbles
        for(int x = 0; x < ARENA_X; ++x)
        {
            for(int y = 0; y < ARENA_Y; ++y)
            {
                std::list<Tribble*>::iterator fit, fitBegin, fitEnd;
                int nNrOfTribbles = arena[x][y].getNumberOfTribbles();

                if (arena[x][y].isFood() && nNrOfTribbles > 0)
                {
                    int nFoodVal = 40 / nNrOfTribbles;
                    fitBegin = arena[x][y].getIteratorBegin();
                    fitEnd = arena[x][y].getIteratorEnd();
                    arena[x][y].setFood(false);
                    for (fit=fitBegin; fit!=fitEnd; ++fit)
                    {
                        if ((*fit)->getEnergy() < 1200)
                        {
                            (*fit)->setEnergy((*fit)->getEnergy() + nFoodVal);
                        }
                    }

                }
            }
        }
    }

    return 0;
}
